import 'dart:io';

import 'package:path/path.dart';

class ProjectStringFinder {
  static ProjectStringFinder instance = ProjectStringFinder();

  List<ProjectDartFile> find() {
    final List<ProjectDartFile> results = [];

    final directory = Directory(join(Directory.current.path, 'lib'));
    if (!directory.existsSync()) {
      return [];
    }

    for (var element in directory.listSync(recursive: true)) {
      if (element is! File) {
        continue;
      }
      if (!element.path.endsWith('.dart')) {
        continue;
      }
      if (element.path.endsWith('.g.dart')) {
        continue;
      }
      if (element.path.contains('/generated/')) {
        continue;
      }
      if (element.path.endsWith('_host_api.dart')) {
        continue;
      }

      // TODO: 如果是已处理过的文件，并且文件修改时间未发生变化，则不处理

      // 寻找文件中的字符串
      final result = ProjectDartFile.fromFile(element);
      if (result.strings.isNotEmpty) {
        results.add(result);
      }
    }

    return results;
  }
}

class ProjectDartFile {
  ProjectDartFile(this.file, this.strings);
  final String file;
  final List<ProjectDartFileString> strings;

  Map<String, dynamic> toJson() {
    return {
      'file': file,
      'strings': strings.map((e) => e.toJson()).toList(),
    };
  }

  factory ProjectDartFile.fromJson(Map<String, dynamic> json) {
    return ProjectDartFile(
      json['file'],
      (json['strings'] as List).map((e) => ProjectDartFileString.fromJson(e)).toList().cast(),
    );
  }

  static ProjectDartFile fromFile(File file) {
    final List<ProjectDartFileString> strings = [];

    final lines = file.readAsLinesSync();
    for (var i = 0; i < lines.length; i++) {
      // 寻找字符串
      final lineText = lines[i];

      // 是否跳过当前行
      if (isSkipLine(lineText)) {
        continue;
      }

      final positions = findStringPositions(lineText);
      if (positions.isEmpty) {
        continue;
      }

      for (var position in positions) {
        final string = lineText.substring(position.start, position.end);
        if (string.isEmpty) {
          continue;
        }

        if (!isValidString(lineText, position, string)) {
          // print('skip ==> $string === $lineText');
          continue;
        }

        if (!isValidAfterText(lineText, position, string) || !isValidBeforeText(lineText, position, string)) {
          // print('skip ==> $string === $lineText');
          continue;
        }

        // if (globalIgnoredStrings.contains(string)) {
        //   continue;
        // }

        // print('line: $i, string: [$string] === [$lineText]');

        strings.add(ProjectDartFileString(i, lineText, string, position.start));
      }
    }
    return ProjectDartFile(file.path, strings);
  }

  static bool isSkipLine(String line) {
    final tirmedLine = line.trim();

    /// 跳过注释行
    if (tirmedLine.startsWith('//')) {
      return true;
    }

    if (tirmedLine.startsWith('import')) {
      return true;
    }

    return false;
  }

  static bool isValidString(String lineText, TextRange range, String string) {
    /// 先检查字符串是否有效

    if (string.startsWith('.')) {
      return false;
    }

    // 作为Key的字符串 分隔 _ - /
    // miniapp_switch_transfer_confirm_click
    if (RegExp(r'[-_/.]').hasMatch(string)) {
      if (!string.contains(' ')) {
        return false;
      }
    }

    // 全大写的字母字符串
    if (RegExp(r'^[A-Z]+$').hasMatch(string)) {
      return false;
    }

    // 全小写的字母字符串
    if (RegExp(r'^[a-z]+$').hasMatch(string)) {
      return false;
    }

    // 是否是有效的英文句子，大写字母必须在首位 或者 前一个字符是标点符号或空格或大写字母
    if (!isValidEnglishSentence(string)) {
      return false;
    }

    // 确保文本至少包含一个中文或英文字母，并且由英文字母、中文字符以及标点符号组成
    if (!RegExp(r'[A-Za-z\u4e00-\u9fa5]').hasMatch(string) ||
        !RegExp(r'^[0-9A-Za-z\u4e00-\u9fa5., ?，。！？；：“”‘’（）《》、]+$').hasMatch(string)) {
      return false;
    }

    return true;
  }

  static bool isValidBeforeText(String lineText, TextRange range, String string) {
    final addQuotesRange = TextRange(start: range.start - 1, end: range.end + 1);
    final before = addQuotesRange.textBefore(lineText).trim();

    // if (before.endsWith('?')) {
    //   return false;
    // }
    if (before.endsWith('{')) {
      return false;
    }
    // args?['fromType']
    if (before.endsWith('[')) {
      return false;
    }
    // 用于数值判断  == '1'
    if (before.endsWith('==') || before.endsWith('!=')) {
      return false;
    }
    // 用于 switch 数值判断
    if (before.endsWith('case')) {
      return false;
    }

    if (before.endsWith('print(')) {
      return false;
    }
    if (before.endsWith('debug(')) {
      return false;
    }
    if (before.contains('debugPrint(')) {
      return false;
    }
    if (before.endsWith('fontFamily:')) {
      return false;
    }
    if (before.startsWith('part')) {
      return false;
    }

    if (before.endsWith('":') || before.endsWith("':")) {
      return false;
    }

    if (before.startsWith('static ')) {
      return false;
    }

    if (before.startsWith('@JsonKey(')) {
      return false;
    }

    return true;
  }

  static bool isValidAfterText(String lineText, TextRange range, String string) {
    final addQuotesRange = TextRange(start: range.start - 1, end: range.end + 1);
    final after = addQuotesRange.textAfter(lineText).trimLeft();

    // Map 中作为Key的字符串
    if (after.startsWith(':')) {
      // 不是 三目运算符
      if (!addQuotesRange.textBefore(lineText).trimRight().endsWith('?')) {
        return false;
      }
    }

    if (after.startsWith(']')) {
      return false;
    }
    if (after.startsWith('}')) {
      return false;
    }
    return true;
  }

  static bool isValidEnglishSentence(String string) {
    RegExp regExp = RegExp(r'[A-Z]');

    Iterable<RegExpMatch> matches = regExp.allMatches(string);
    if (matches.isEmpty) {
      // 没有大写字母，但是有中文字符
      if (RegExp(r'[\u4e00-\u9fa5]').firstMatch(string) != null) {
        return true;
      } else {
        return false;
      }
    }

    for (var element in matches) {
      if (element.start == 0) {
        continue;
      }
      final char = string[element.start - 1];
      if (!char.contains(RegExp(r'[A-Z .,;!?-]'))) {
        return false;
      }
    }
    return true;
  }

  static List<TextRange> findStringPositions(String code) {
    List<TextRange> positions = [];
    bool inString = false;
    int inStringIndex = -1;
    String currentString = '';

    bool inParam = false;

    for (int i = 0; i < code.length; i++) {
      String char = code[i];
      if (inParam) {
        if (char == '}') {
          inParam = false;
        }
        continue;
      } else {
        if (char == '\$') {
          if (i < code.length - 2 && code[i + 1] == '{') {
            inParam = true;
            continue;
          }
        }
      }
      if (inString) {
        if (char == currentString && code[i - 1] != '\\') {
          inString = false;
          positions.add(TextRange(
            start: inStringIndex + currentString.length,
            end: i,
          ));
        }
      } else {
        if (char == '"' || char == "'") {
          inString = true;
          inStringIndex = i;
          currentString = char;
        }
      }
    }
    return positions;
  }
}

class ProjectDartFileString {
  ProjectDartFileString(this.lineNumber, this.lineText, this.string, this.start);
  final int lineNumber;
  final String lineText;
  final String string;
  final int start;

  Map<String, dynamic> toJson() {
    return {
      'lineNumber': lineNumber,
      'lineText': lineText,
      'string': string,
      'start': start,
    };
  }

  factory ProjectDartFileString.fromJson(Map<String, dynamic> json) {
    return ProjectDartFileString(
      json['lineNumber'],
      json['lineText'],
      json['string'],
      json['start'],
    );
  }
}

/// A range of characters in a string of text.
class TextRange {
  /// Creates a text range.
  ///
  /// The [start] and [end] arguments must not be null. Both the [start] and
  /// [end] must either be greater than or equal to zero or both exactly -1.
  ///
  /// The text included in the range includes the character at [start], but not
  /// the one at [end].
  ///
  /// Instead of creating an empty text range, consider using the [empty]
  /// constant.
  const TextRange({
    required this.start,
    required this.end,
  })  : assert(start >= -1),
        assert(end >= -1);

  /// A text range that starts and ends at offset.
  ///
  /// The [offset] argument must be non-null and greater than or equal to -1.
  const TextRange.collapsed(int offset)
      : assert(offset >= -1),
        start = offset,
        end = offset;

  /// A text range that contains nothing and is not in the text.
  static const TextRange empty = TextRange(start: -1, end: -1);

  /// The index of the first character in the range.
  ///
  /// If [start] and [end] are both -1, the text range is empty.
  final int start;

  /// The next index after the characters in this range.
  ///
  /// If [start] and [end] are both -1, the text range is empty.
  final int end;

  /// Whether this range represents a valid position in the text.
  bool get isValid => start >= 0 && end >= 0;

  /// Whether this range is empty (but still potentially placed inside the text).
  bool get isCollapsed => start == end;

  /// Whether the start of this range precedes the end.
  bool get isNormalized => end >= start;

  /// The text before this range.
  String textBefore(String text) {
    assert(isNormalized);
    return text.substring(0, start);
  }

  /// The text after this range.
  String textAfter(String text) {
    assert(isNormalized);
    return text.substring(end);
  }

  /// The text inside this range.
  String textInside(String text) {
    assert(isNormalized);
    return text.substring(start, end);
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) {
      return true;
    }
    return other is TextRange && other.start == start && other.end == end;
  }

  @override
  int get hashCode => Object.hash(
        start.hashCode,
        end.hashCode,
      );

  @override
  String toString() => 'TextRange(start: $start, end: $end)';
}
